/*
j = 0
r = RippleMidiInSync.new
r.registerRepeater({j = j + 1; ("repeat " + j).postln;}, 1/16);

r.repeaters = []

*/
RippleMidiInSync{

	classvar	<>ticksPerBeat = 24;	// MIDI clock standard
	classvar tempoWindow = 60;
	classvar instance;
	var mode = 0; // 0 = stopped, 1 = playing
	var <>repeaters, ticks = -1;
	var tempoTimeStamp;

	*new{
		if(
			instance == nil,
			{
				instance = super.new.init
			}
		);
		^instance;
	}
	
	*instance{
		^this.new;
	}
	
	init{
		MIDIIn.sysrt = {|src, chan, val|this.sysrt(chan)};
		repeaters = [];
	}

	hasTempo{
		^ticks > 0;
	}

	sysrt{|chan|
		chan.switch(
			8, {this.tick},
			10, {this.start},
			11, {this.resume},
			12, {this.stop}
		)
	}
	
	tick{
		if(mode == 1){
			ticks = ticks + 1;
		};
		repeaters.do({|item|
			if( ticks % (item[1] * RippleMidiInSync.ticksPerBeat)== 0 ){
				item[0].value;
			}	
		});
		if(ticks % tempoWindow == 0){
			tempoTimeStamp = SystemClock.seconds;		
		}
	}

	start{
		ticks = -1;
		mode = 1;
	}
	
	resume{
		mode = 1;
	}
	
	stop{
		mode = 0;
	}
	
	registerRepeater{|function, beatInterval|
		repeaters = repeaters.add([function, beatInterval])	
	}
	
	deregisterRepeater{|callback|
		var elemToRemove;
		repeaters.do{|elem|
			if(elem[0] == callback){
				elemToRemove = elem	
			}
		};
		if(elemToRemove.notNil){
			repeaters.remove(elemToRemove);	
		}
	}
	
	getBeatDuration{
		var ticksElepsed, elapsedTime;
		
		if(ticks <= 0){
			NoTempoYetError("Can't call RippleMidiSync.getBeatDuration before sending some beat info").throw;
			^nil;		
		};
		
		ticksElepsed = ticks  % tempoWindow;
		if(ticksElepsed == 0){
			ticksElepsed = tempoWindow;
		};
		elapsedTime = SystemClock.seconds - tempoTimeStamp;
		
		^elapsedTime * RippleMidiInSync.ticksPerBeat / ticksElepsed; 
		
	}
	
}

NoTempoYetError : Error{
	
}